/*
 *    Copyright © OpenAtom Foundation.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package com.inspur.edp.bef.core.session;

import com.inspur.edp.cef.core.common.LongGenerator;
import com.inspur.edp.cef.entity.accessor.base.AccessorComparer;
import io.iec.edp.caf.commons.runtime.CafEnvironment;
import io.iec.edp.caf.commons.utils.SpringBeanUtils;
import io.iec.edp.caf.core.session.ICafSessionService;
import io.iec.edp.caf.core.session.core.CAFSessionThreadHolder;
import io.iec.edp.caf.msu.api.ServiceUnitAwareService;
import io.iec.edp.caf.runtime.sessiongroup.api.data.CommonVariableEntity;
import io.iec.edp.caf.runtime.sessiongroup.api.manager.RuntimeCommonVariableService;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import java.util.stream.Collectors;
import javax.xml.bind.DatatypeConverter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
import org.springframework.security.core.context.SecurityContextHolder;

@Slf4j
public final class FuncSessionUtil {

//  private static final RuntimeCommonVariableService service = SpringBeanUtils
//      .getBean(RuntimeCommonVariableService.class);

  public static String getTenantId() {
    //Java版临时注释 租户接口?
    return "";
  }

  public static boolean isTokenExpired(String tokenId) {
    CommonVariableEntity entity = SpringBeanUtils.getBean(RuntimeCommonVariableService.class)
        .getCommonVariableEntity(tokenId);
    String sid = (String) entity.getItems().get("sessionId");
    return SpringBeanUtils.getBean(ICafSessionService.class).isExpired(sid);
  }

  public static List<String> isTokenExpired(Set<String> tokenIds) {
    Objects.requireNonNull(tokenIds, "tokenIds");
    final int BATCH_SIZE = 20;
    ArrayList<String> isTokenExpiredList = new ArrayList<>();
    //记录session及相对应的tokenId信息
    HashMap<String, List<String>> sessionRelations = new HashMap<>(32);
    int tokenIdSize = tokenIds.size();
    RuntimeCommonVariableService tokenService = SpringBeanUtils
        .getBean(RuntimeCommonVariableService.class);
    ICafSessionService sessionService = SpringBeanUtils.getBean(ICafSessionService.class);
    for (String tokenId : tokenIds) {
      CommonVariableEntity entity = tokenService.getCommonVariableEntity(tokenId);
      String sessionId = (String) entity.getItems().get("sessionId");
      List<String> tokensOfSession = sessionRelations.get(sessionId);
      if (tokensOfSession == null) {
        tokensOfSession = new ArrayList<>();
        sessionRelations.put(sessionId, tokensOfSession);
      }
      tokensOfSession.add(tokenId);
      tokenIdSize--;
      if (sessionRelations.size() < BATCH_SIZE && tokenIdSize > 0) {
        continue;
      }
      List<String> timeoutSessionIds = sessionService.filterExpired(
          sessionRelations.keySet().stream().collect(Collectors.toList()));
      if (timeoutSessionIds != null && timeoutSessionIds.size() > 0) {
        for (String timeoutSessionId : timeoutSessionIds) {
          isTokenExpiredList.addAll(sessionRelations.get(timeoutSessionId));
        }
      }
      sessionRelations.clear();
    }
    return isTokenExpiredList;
  }

  public static boolean redisEnabled() {
    return CafEnvironment.getEnvironment().getProperty("redis.enabled", Boolean.class, true);
  }

  public static boolean msuEnabled(String msu) {
    List<String> suList = SpringBeanUtils.getBean(ServiceUnitAwareService.class)
        .getEnabledServiceUnits();
    return suList != null && suList.stream().anyMatch(item -> item != null && item.equalsIgnoreCase(msu));
  }

  public static void startScheduler(Class schedulerClass, String name, int minutes, int rand) {
    startScheduler(schedulerClass, name,
        minutes + ((int) LongGenerator.get()) % (minutes / rand) * rand);
  }

  @SneakyThrows
  public static void startScheduler(Class schedulerClass, String name, int minutes) {
    JobDetail jobDetail = JobBuilder.newJob(schedulerClass).withIdentity(name, "befGroup").build();
    Trigger trigger = TriggerBuilder.newTrigger().withIdentity(name + "trigger", "befGroup")
        .withSchedule(
            SimpleScheduleBuilder.simpleSchedule().withIntervalInMinutes(minutes).repeatForever())
        .build();
    //创建调度器
    Properties quartzProperties = new Properties();
    //并行启动不开redis情况下调度任务不清楚为何走了持久化模型, 强制指定内存模式
    quartzProperties.put("org.quartz.scheduler.instanceName", "BEF_CLUSTERED_JOB_SCHEDULER");
    quartzProperties.put("org.quartz.scheduler.instanceId", "AUTO");
    quartzProperties.put("org.quartz.scheduler.rmi.export", "false");
    quartzProperties.put("org.quartz.scheduler.rmi.proxy", "false");
    quartzProperties.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
    quartzProperties.put("org.quartz.threadPool.threadCount", "10");
    quartzProperties.put("org.quartz.threadPool.threadPriority", "5");
    quartzProperties.put("org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread", "true");
    quartzProperties.put("org.quartz.jobStore.misfireThreshold", "60000");
    quartzProperties.put("org.quartz.scheduler.wrapJobExecutionInUserTransaction", "false");
    quartzProperties.put("org.quartz.jobStore.class", "org.quartz.simpl.RAMJobStore");
    SchedulerFactory schedulerFactory = new StdSchedulerFactory(quartzProperties);
    Scheduler scheduler = schedulerFactory.getScheduler();
    scheduler.deleteJob(jobDetail.getKey());
    //将任务及其触发器放入调度器
    scheduler.scheduleJob(jobDetail, trigger);
    //调度器开始调度任务
    scheduler.startDelayed(minutes * 60);
  }

  public static String createCombinedId(String sessionId, String suCode) {
    return sessionId + "." + suCode;
  }

  public static String getSessionIdBySplit(String combinedId) {
    return combinedId.substring(0, combinedId.indexOf("."));
  }

  public static String getSuCodeBySplit(String combinedId) {
    return combinedId.substring(combinedId.indexOf(".") + 1);
  }

  public static void clearCafSession() {
    CAFSessionThreadHolder.purge();
    SecurityContextHolder.clearContext();
  }

  static <T> boolean compareMap(Map<String, T> src, Map<String, T> tar) {
    if (src == null || src.isEmpty()) {
      return tar == null || tar.isEmpty();
    } else {
      if (tar == null || tar.isEmpty() || src.size() != tar.size()) {
        return false;
      }
      for (Entry<String, T> pair : src.entrySet()) {
        if (!(pair.getValue() instanceof String)) {
          return false;
        }
        T tarValue = tar.get(pair.getKey());
        if (!(tarValue instanceof String)) {
          return false;
        }
        if (!AccessorComparer.equals(pair.getValue(), tarValue)) {
          return false;
        }
      }
    }
    return true;
  }

  @SneakyThrows
  static String convertMap2Binary(Map map) {
    if (map == null) {
      return "";
    }
    ByteArrayOutputStream os = null;
    ObjectOutputStream oos = null;
    try {
      os = new ByteArrayOutputStream();
      oos = new ObjectOutputStream(os);
      oos.writeObject(map);
      oos.flush();
      return DatatypeConverter.printBase64Binary(os.toByteArray());
//      return os.toString(StandardCharsets.UTF_8.name());
    } finally {
      if (oos != null) {
        oos.close();
      }
      if (os != null) {
        os.close();
      }
    }
  }

  @SneakyThrows
  public static <T> T convertFromBinary(String value) {
    ByteArrayInputStream is = null;
    ObjectInputStream ios = null;
    try {
      is = new ByteArrayInputStream(DatatypeConverter.parseBase64Binary(value));
      ios = new ObjectInputStream(is);
      return (T)ios.readObject();
    }finally {
      if(is != null)
        is.close();
      if(ios != null)
        ios.close();
    }
  }
}
